Skip to content

arrow-buffer: i256: implement ilog - #9453

Merged
alamb merged 9 commits into
apache:mainfrom
theirix:i256-pow
Jun 3, 2026
Merged

arrow-buffer: i256: implement ilog#9453
alamb merged 9 commits into
apache:mainfrom
theirix:i256-pow

Conversation

@theirix

@theirix theirix commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Implementation of integer logarithm. There is no matching num_traits trait, but this implementation provides a good motivation for such a trait.

What changes are included in this PR?

  • No external dependencies
  • Checked methods (log, log2, log10)
  • Unchecked methods (panic by design)

Are these changes tested?

  • Unit tests

Are there any user-facing changes?

No

@github-actions github-actions Bot added the arrow Changes to the arrow crate label Feb 20, 2026
@theirix
theirix marked this pull request as ready for review February 20, 2026 22:19
Comment thread arrow-buffer/src/bigint/mod.rs Outdated
/// Returns `None` if `self` is less than or equal to zero, or if `base` is less than 2.
#[inline]
pub fn checked_ilog(self, base: i256) -> Option<u32> {
if self <= Self::ZERO || base < i256::from(2) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these invariants are checked by the call to ExtI256::checked_ilog anyway, what's the point of restating the logic here? The to_ext_i256 calls should be no-ops anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason was to place checks closer to the caller. For procmacros of i256, it's less clear from the source code, but the documentation explicitly lists the required checks, and we repeat them in a docstring. Removed extra code.

Comment thread arrow-buffer/src/bigint/mod.rs Outdated
/// Returns `None` if `self` is less than or equal to zero.
#[inline]
pub fn checked_ilog10(self) -> Option<u32> {
// Switch to I256::checked_ilog10 when I256 switches MSRV

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't it be used now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked_ilog10 is literally commented out in i256 code: Alexhuszagh/i256-rs@85c0bb1/src/int/checked.rs#L102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I only checked checked_ilog2. My mistake :)

Comment thread arrow-buffer/src/bigint/mod.rs Outdated
/// Returns `None` if `self` is less than or equal to zero.
#[inline]
pub fn checked_ilog2(self) -> Option<u32> {
// Switch to I256::checked_ilog2 when I256 switches MSRV

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: why not now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For checked_ilog2 - it exists, updated the call.

Comment thread arrow-buffer/src/bigint/mod.rs Outdated
// log10 should be 76 or 77
let result = large.checked_ilog(i256::from(10));
assert!(result.is_some());
assert!(result.unwrap() >= 76 && result.unwrap() <= 77);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't an assert_eq! be used here? Surely the result is deterministic?

@alamb

alamb commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

I am not sure about adding a new dependency on the i256 crate. It seems that it is not widely used across the ecosystem

https://crates.io/crates/i256
https://crates.io/crates/i256/reverse_dependencies

This is the kind of crate that I worry will end up having a RUSTSEC against it (due to unmaintained, etc) and I think will add an additional maintenance burden

@theirix

theirix commented Apr 22, 2026

Copy link
Copy Markdown
Contributor Author

I am not sure about adding a new dependency on the i256 crate. It seems that it is not widely used across the ecosystem

crates.io/crates/i256 crates.io/crates/i256/reverse_dependencies

This is the kind of crate that I worry will end up having a RUSTSEC against it (due to unmaintained, etc) and I think will add an additional maintenance burden

That's true - it is much less popular than other math libraries. I could suggest that the bnum crate is much more widely used and provides the required primitives. If we prefer to avoid a dependency on a smaller crate, it's pretty simple to implement arithmetic manually, as is already done with checked_pow.

@alamb

alamb commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

That's true - it is much less popular than other math libraries. I could suggest that the bnum crate is much more widely used and provides the required primitives. If we prefer to avoid a dependency on a smaller crate, it's pretty simple to implement arithmetic manually, as is already done with checked_pow.

This would be my preference -- "a little copying vs a little of dependency"

https://www.youtube.com/clip/UgkxWCEmMJFW0-TvSMzcMEAHZcpt2FsVXP65

@theirix

theirix commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

That's true - it is much less popular than other math libraries. I could suggest that the bnum crate is much more widely used and provides the required primitives. If we prefer to avoid a dependency on a smaller crate, it's pretty simple to implement arithmetic manually, as is already done with checked_pow.

This would be my preference -- "a little copying vs a little of dependency"

youtube.com/clip/UgkxWCEmMJFW0-TvSMzcMEAHZcpt2FsVXP65

Thank you, that's insightful! I'll update the implementation shortly.

@theirix

theirix commented May 24, 2026

Copy link
Copy Markdown
Contributor Author

That's true - it is much less popular than other math libraries. I could suggest that the bnum crate is much more widely used and provides the required primitives. If we prefer to avoid a dependency on a smaller crate, it's pretty simple to implement arithmetic manually, as is already done with checked_pow.

This would be my preference -- "a little copying vs a little of dependency"
youtube.com/clip/UgkxWCEmMJFW0-TvSMzcMEAHZcpt2FsVXP65

Thank you, that's insightful! I'll update the implementation shortly.

@alamb I've switched to the native i256 support without external deps. Could you please re-review?

@alamb alamb changed the title arrow-buffer: i256: implement ilog via i256 crate arrow-buffer: i256: implement ilog Jun 2, 2026
alamb and others added 2 commits June 2, 2026 13:34
Extends the existing `test_ilog` with cases that the current tests do
not cover: small results (0 and 1) for non-base-10 bases, including
`self == base`. Per the std contract `n.ilog(n) == 1` (e.g.
`2u32.ilog(2) == 1`), but `i256::checked_ilog` currently returns 0 for
these inputs. Also cross-checks small results against `u128::ilog` as
ground truth.

These tests fail until the following fix is applied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`checked_ilog` returned the early `Some(0)` for `self == base`, but the
correct result is 1 (`base^1 == base <= self`), matching the std
contract `n.ilog(n) == 1`. The base-10 path was unaffected since it is
handled by `checked_ilog10`, so this affected every other base
(including `ilog2(2)`).

Narrow the early return from `self <= base` to `self < base`; the loop
already computes the correct value for `self == base`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @theirix and @Rafferty97 -- this is very close, but i think I found a bug (ilog(n, n) should be 1, not 0). Please see this PR:

Otherwise this looks good to me.

Comment thread arrow-buffer/src/bigint/mod.rs Outdated
if base <= Self::ONE {
return None;
}
if self <= base {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't ilog(n, n) be 1 (not zero)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: yes I think it should -- here is a PR that fixes it on your forl:

arrow-buffer: i256: fix ilog off-by-one when self == base
@theirix

theirix commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @theirix and @Rafferty97 -- this is very close, but i think I found a bug (ilog(n, n) should be 1, not 0). Please see this PR:

Otherwise this looks good to me.

Thank you very much, @alamb, it's a good spot! Merged your fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @theirix and @Rafferty97

I pushed a small fmt fix to get CI to pass and I think this looks good to me now

THanks again

@alamb
alamb merged commit 97f4b14 into apache:main Jun 3, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arrow Changes to the arrow crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

arrow-buffer: implement log for i256

3 participants